﻿/***************
 * Copyright 2009 (C) 
 * Timur Fanshteyn
 * Blog: http://blog.tfanshteyn.com
 * ************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Tangosol.Util;
using Tangosol.Util.Filter;
using System.Collections;
using System.Reflection;

namespace Coherence.Linq
{
    public class TranslateResult
    {
        public SelectExpression Source { get; set; }
        public Expression Where { get; set; }
        public LambdaExpression Projector { get; set; }
    }

    public class CoherenceQueryTranslator : ExpressionVisitor
    {
        Dictionary<ParameterExpression, Expression> map;

        public CoherenceQueryTranslator()
        {
        }

        public TranslateResult Translate(Expression expression)
        {
            this.map = new Dictionary<ParameterExpression, Expression>();
            ProjectionExpression expr = (ProjectionExpression )this.Visit(expression);
            SelectExpression chExpr = (SelectExpression)expr.Source;
            Expression whereExpr = null;
            LambdaExpression projector=null;
            if (chExpr is WhereExpression)
            {
                whereExpr = chExpr;
                chExpr = null;
                projector = new ProjectionBuilder().Build(expr.Projector);
            }
            else
            {
                whereExpr = ((SelectExpression)(chExpr.From)).Where; 
                projector = new ProjectionBuilder().Build(expr.Projector);
            }
            
            return new TranslateResult()
            {
                Source = chExpr,
                Where = whereExpr,
                Projector = projector
            };
        }

        private static Expression StripQuotes(Expression e)
        {
            while (e.NodeType == ExpressionType.Quote)
            {
                e = ((UnaryExpression)e).Operand;
            }
            return e;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method.DeclaringType == typeof(Queryable))
            {
                if (m.Method.Name == "Where")
                {
                    return this.BindWhere(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]));
                }
                else if (m.Method.Name == "Select")
                {
                    return this.BindSelect(m.Type, m.Arguments[0], (LambdaExpression)StripQuotes(m.Arguments[1]));
                }
                else if (m.Method.Name == "Join")
                {
                    Expression outerProjection = (Expression)this.Visit(m.Arguments[0]);
                    Expression innerProjection = (Expression)this.Visit(m.Arguments[1]);
                    return m;
                }
            }
            return base.VisitMethodCall(m);
        }

        private Expression BindSelect(Type resultType, Expression source, LambdaExpression selector)
        {
            ProjectionExpression projection = (ProjectionExpression)this.Visit(source);
            Expression expression = this.Visit(selector.Body);
            return new ProjectionExpression(
                new SelectExpression(resultType, projection.Source, null),
                selector
                );
        }

        private Expression BindWhere(Type resultType, Expression source, LambdaExpression predicate)
        {
            LambdaExpression pred = (LambdaExpression) this.Visit(predicate);
            return new ProjectionExpression(new WhereExpression(resultType, source, pred),
                predicate.Parameters[0]);
        }

        private bool IsTable(object value)
        {
            IQueryable q = value as IQueryable;
            return q != null && q.Expression.NodeType == ExpressionType.Constant;
        }

        private ProjectionExpression GetTableProjection(object value)
        {
            IQueryable table = (IQueryable)value;
            Type resultType = typeof(IEnumerable<>).MakeGenericType(table.ElementType);
            return new ProjectionExpression(new SelectExpression(resultType, table.Expression, null), table.Expression);
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            if (this.IsTable(c.Value))
            {
                return GetTableProjection(c.Value);
            }

            return c;

        }

        protected override Expression VisitParameter(ParameterExpression p)
        {
            Expression e;
            if (this.map.TryGetValue(p, out e))
            {
                return e;
            }
            return p;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            Expression source = this.Visit(m.Expression);

            switch (source.NodeType)
            {
                case ExpressionType.MemberInit:
                    MemberInitExpression min = (MemberInitExpression)source;
                    for (int i = 0, n = min.Bindings.Count; i < n; i++)
                    {
                        MemberAssignment assign = min.Bindings[i] as MemberAssignment;
                        if (assign != null && MembersMatch(assign.Member, m.Member))
                        {
                            return assign.Expression;
                        }
                    }
                    break;
                case ExpressionType.New:
                    NewExpression nex = (NewExpression)source;
                    if (nex.Members != null)
                    {
                        for (int i = 0, n = nex.Members.Count; i < n; i++)
                        {
                            if (MembersMatch(nex.Members[i], m.Member))
                            {
                                return nex.Arguments[i];
                            }
                        }
                    }
                    break;
            }
            if (source == m.Expression)
            {
                return m;
            }
            return MakeMemberAccess(source, m.Member);
        }

        private bool MembersMatch(MemberInfo a, MemberInfo b)
        {
            if (a == b)
            {
                return true;
            }

            if (a is MethodInfo && b is PropertyInfo)
            {
                return a == ((PropertyInfo)b).GetGetMethod();
            }
            else if (a is PropertyInfo && b is MethodInfo)
            {
                return ((PropertyInfo)a).GetGetMethod() == b;
            }
            return false;
        }

        private Expression MakeMemberAccess(Expression source, MemberInfo mi)
        {
            FieldInfo fi = mi as FieldInfo;
            if (fi != null)
            {
                return Expression.Field(source, fi);
            }
            PropertyInfo pi = (PropertyInfo)mi;
            return Expression.Property(source, pi);
        }
    }
}